#!/usr/bin/env python
#Copyright (c) 2011 Isaac Dawson (WBTS Project)
#Permission is hereby granted, free of charge, to any person obtaining a copy 
#of this software and associated documentation files (the "Software"), to deal 
#in the Software without restriction, including without limitation the rights 
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
#copies of the Software, and to permit persons to whom the Software is furnished
#to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
#THE SOFTWARE.
from twisted.trial import unittest
from twisted.internet import defer, base
from wbts.storage import storage

class StorageTest(object):
    default_tbl = 'test_tbl'
    items = [
            {'teststr': 'asdf', 'testint': 1},
            {'teststr': 'asdf2', 'testint': 2},
            {'teststr': 'asdf3', 'testint': 3},
            {'teststr': 'asdf4', 'testint': 4},
            {'teststr': 'asdf5', 'testint': 5},
            {'teststr': 'asdf6', 'testint': 6},
            {'test': 'asdf', 'test': 0}
    ]
        
    # HELPER FUNCS
    def createContents(self, results, callback):
        def insert_contents(results, callback):
            self.assertEqual(results, 0, "created table returned 0")
            d = self.db.test_tbl.insert(self.items)
            d.addCallback(callback)
            return d
        d = self.db.createTable(self.default_tbl)
        d.addCallback(insert_contents, callback)
        return d
    
    def createTable(self, results, callback):
        d = self.db.createTable(self.default_tbl)
        d.addCallback(callback)
        return d
    
    def createBadTable(self, dummy, badinput):
        def check_result(res):
            self.assertFailure(res, storage.WBTSStorageException)
        d = self.db.createTable(badinput)
        d.addCallback(check_result)
        self.assertFailure(d, storage.WBTSStorageException)
        return
    
    def returnDropGood(self, result):
        self.assertEqual(result, 0, "should return 0 for good table")
        return
        
    def returnDropBad(self, result):
        self.assertEqual(result, 1, "should return 1 for bad table")
            
    def returnCreate(self, dummy, tbl_name, drop_func):
        d = self.db.dropTable(tbl_name)
        d.addCallback(drop_func)
        return
        
    def createBadTable(self, dummy, badinput):
        def check_result(res):
            self.assertFailure(res, storage.WBTSStorageException)
        d = self.db.createTable(badinput)
        d.addCallback(check_result)
        self.assertFailure(d, storage.WBTSStorageException)
        return
    # END HELPER FUNCS
    
    # START GENERIC TESTS
    def testCreateTable(self):
        def check_result(result):
            self.assertIsInstance(result, int)
            return
        d = self.db.connect()
        d.addCallback(lambda result:
            self.db.createTable(self.default_tbl).addCallback(check_result))
        return d
    
      
    def testCreateEmptyTable(self):
        d = self.db.connect()
        d.addCallback(self.createBadTable, '')
        return d
    
    def testCreateIntegerTable(self):
        d = self.db.connect()
        d.addCallback(self.createBadTable, 1234)
        return d
    
    def testDropTable(self):        
        # good table, this should pass
        d = self.db.connect()
        d.addCallback(lambda dummy:
            self.db.createTable(self.default_tbl).addCallback(self.returnCreate,
                                                              self.default_tbl,
                                                              self.returnDropGood))
        return d
    
    def testEmptyTable(self):
        # empty table
        d = self.db.connect()
        d.addCallback(lambda dummy:
            self.db.createTable(self.default_tbl).addCallback(self.returnCreate,
                                                              '',
                                                              self.returnDropBad))
        return d
    def testDropNonExistantTable(self):    
        # Non-existent table
        d = self.db.connect()
        d.addCallback(lambda dummy:
            self.db.createTable(self.default_tbl).addCallback(self.returnCreate,
                                                              'asdf',
                                                              self.returnDropBad))
        return d
    
    def testAddContents(self):
        def check_result(result):
            self.assertIsInstance(result, int)
            return
        d = self.db.connect()
        d.addCallback(self.createContents, check_result)
        return d

    def testFindEmptySearch(self):
        def check_results(result):
            self.assertEquals(result, [], 'result should be empty list.')
            return
        
        def run_tests(result):
            self.assertEqual(result, 0, "insert should have returned 0")
            self.assertEqual(hasattr(self.db, self.default_tbl), True)
            d = self.db.test_tbl.find({'x':'x'})
            d.addCallback(check_results)
            return d
        
        d = self.db.connect()
        d.addCallback(self.createContents, run_tests)
        return d
    
    def testFindSearch(self):
        def check_results(result):
            self.assertEquals(len(result), 1, 'result should have one value.')
            return
        
        def run_tests(result):
            self.assertEqual(result, 0, "insert should have returned 0")
            self.assertEqual(hasattr(self.db, self.default_tbl), True)
            d = self.db.test_tbl.find({'teststr':'asdf'})
            d.addCallback(check_results)
            return d
        
        d = self.db.connect()
        d.addCallback(self.createContents, run_tests)
        return d
    
    def testFindSkip(self):
        def check_results(result):
            self.assertEquals(len(result), 3, 'result should have 3 records.')
            return
        
        def run_tests(result):
            self.assertEqual(result, 0, "insert should have returned 0")
            d = self.db.test_tbl.find(skip=0, limit=3)
            d.addCallback(check_results)
            return d
        
        d = self.db.connect()
        d.addCallback(self.createContents, run_tests)
        return d
    
    def testFindSkipSingle(self):
        def check_results(result):
            self.assertEquals(len(result), 1, 'result should have 1 record')
            return
        
        def run_tests(result):
            self.assertEqual(result, 0, "insert should have returned 0")
            # we can only return how many records we have... since we should
            # only have 7 records, we can only return 1 record.
            d = self.db.test_tbl.find(skip=6, limit=3)
            d.addCallback(check_results)
            return d
        
        d = self.db.connect()
        d.addCallback(self.createContents, run_tests)
        return d
    
        
    def testFindInvalidSkip(self):
        def check_results(result):
            self.assertEquals(result, None, 'result should be None type.')
            return
        
        def run_tests(result):
            self.assertEqual(result, 0, "insert should have returned 0")
            d = self.db.test_tbl.find(skip=-1, limit=0)
            d.addCallback(check_results)
            return d
        
        d = self.db.connect()
        d.addCallback(self.createContents, run_tests)
        return d
    
    def testFindOneEmpty(self):
        def check_results(result):
            self.assertEquals(result, {}, 'result should be empty dict.')
            return
        
        def run_tests(result):
            self.assertEqual(result, 0, "create should have returned 0")
            self.assertEqual(hasattr(self.db, self.default_tbl), True)
            d = self.db.test_tbl.find_one({'teststr':'asdfasdfasdf'})
            d.addCallback(check_results)
            return d
        
        d = self.db.connect()
        d.addCallback(self.createTable, run_tests)
        return d
    
    def testFindOne(self):
        def check_results(result):
            self.assertEquals(len(result), 3, 'result should be a dict (3 items).')
            return
        
        def run_tests(result):
            self.assertEqual(result, 0, "create should have returned 0")
            self.assertEqual(hasattr(self.db, self.default_tbl), True)
            d = self.db.test_tbl.find_one({'teststr':'asdf'})
            d.addCallback(check_results)
            return d
        
        d = self.db.connect()
        d.addCallback(self.createContents, run_tests)
        return d
    
    def testRemove(self):
        def check_results(result):
            self.assertEquals(result, len(self.items)-1,
                              'result should be one less than total items.')
            return
        
        def get_count(result):
            self.assertEquals(result, 0, "remove should return 0")
            d = self.db.test_tbl.count()
            d.addCallback(check_results)
            return d
        
        def run_tests(result):
            self.assertEqual(result, 0, "create should have returned 0")
            self.assertEqual(hasattr(self.db, self.default_tbl), True)
            d = self.db.test_tbl.remove({'testint': 1})
            d.addCallback(get_count)
            return d
        
        d = self.db.connect()
        d.addCallback(self.createContents, run_tests)
        return d
    
    def testRemoveEmpty(self):        
        def get_count(result):
            self.assertEquals(result, 0, "remove should return 0")
            return 
        
        def run_tests(result):
            self.assertEqual(result, 0, "create should have returned 0")
            self.assertEqual(hasattr(self.db, self.default_tbl), True)
            d = self.db.test_tbl.remove({'testint': 1})
            d.addCallback(get_count)
            return d
        
        d = self.db.connect()
        d.addCallback(self.createContents, run_tests)
        return d
    
    def testDrop(self):
        def check_drop(result):
            self.assertEquals(hasattr(self.db, self.default_tbl), False)
            return
        
        def run_tests(result):
            self.assertEqual(result, 0, "create should have returned 0")
            self.assertEqual(hasattr(self.db, self.default_tbl), True)
            d = self.db.dropTable(self.default_tbl)
            d.addCallback(check_drop)
            return d
        
        d = self.db.connect()
        d.addCallback(self.createContents, run_tests)
        return d
